Analysis of dead cell fractions

In part 1 (VLW_V23_combined_analysis.Rmd), all data were organized to include drug annotation and image file paths, in addition to cell count and ch2-pos counts. Use these data to examine time and drug concentration effects on cell death kinetics.

library(diprate)

OVERWRITE <- FALSE

For these analyses we do not need file_name

d <- read.csv("../data/VLW_V23ab_dataset.csv", as.is=TRUE)
d <- d[order(d$uid,d$time),]
d$file_name <- NULL

Examine the data from the previously identified hits. Focus on two cell lines. 16T best-looking 3D structures, 21T most clinically relevant, but worst looking and slowest growing.

mydrugs <- c("bortezomib", "ixazomib", "YM155", "Zibotentan", "Romidepsin", "cabazitaxel", "delanzomib",
             "ispinesib", "KX2391", "triptolide", "carfilzomib", "rigosertib", "pralatrexate")
mycl <- c("16T","21T")
s2d <- lapply(mycl, function(cl) d[d$drug1 %in% mydrugs & 
                                      d$cell.line==cl & 
                                      grepl("V2",d$uid),])

Save drug subsets into objects.

a <- subset(s2d[[1]], drug1 %in% mydrugs)
b <- subset(s2d[[2]], drug1 %in% mydrugs)
s3d <- lapply(mycl, function(n) d[d$drug1 %in% mydrugs & 
                                      d$cell.line==n & 
                                      grepl("V3",d$uid),])

Screen 3D data by cell count > 40

Many 3D cultures had few, if any, cells in the field of view. Must exclude.

a3 <- subset(s3d[[1]], drug1 %in% mydrugs & orig.cell.count >= 40)
b3 <- subset(s3d[[2]], drug1 %in% mydrugs & orig.cell.count >= 40)

16T 2D

Examine the death kinetics (fraction of ch2 positive cells over time).

plotAllCh2(list("16T"=a), count_cn="orig.cell.count")

16T 3D

plotAllCh2(list("16T"=a3), count_cn="orig.cell.count")

21T 2D

plotAllCh2(list("21T"=b), count_cn="orig.cell.count")

21T 3D

plotAllCh2(list("21T"=b3), count_cn="orig.cell.count")

Apply simple logistic model to death kinetics data

myl3 <- function(x, min=0,max=0.5,mid_x=48, dr=0.05) max/(1 + exp(-dr*(x-mid_x))) 
curve(myl3, from=0, to=120)

Use DRC 3-param logistic function to fit data

The drc library has a number of dose–response model fitting algorithms. The L.3 model is a 3-parameter logistic (not log-logistic) model that can be applied to the time course data.

Test this with a subset of the data (21T + delanzomib).

The model is trying to explain the change in dead cell fraction over time.

mydat <- b[b$drug1=="delanzomib",]
test.m <- drc::drm(ch2.pos/orig.cell.count ~ time, factor(signif(log10(drug1.conc),3)), 
                   data = mydat, fct = drc::L.3())

plot(test.m, legendPos=c(20,0.85), log="")

Extract maximum dead cell fraction from curves

Pull from fit coefficients (upper bound; coefficient “d”).

myconc <- names(coef(test.m))[grep("d",names(coef(test.m)))]
myconc <- gsub("d:","",myconc)
myconc <- as.numeric(myconc)

plot(myconc, coef(test.m)[grep("d",names(coef(test.m)))], ylab="Max dead fraction", main="delanzomib")

Examine an different drug

Determine whether predicted time course trajectories match those of the ixazomib-treated cells.

mydat <- b[b$drug1=="ixazomib",]
test.m <- drc::drm(ch2.pos/orig.cell.count ~ time, factor(signif(log10(drug1.conc),3)), 
                   data = mydat, fct = drc::L.3())

plot(test.m, legendPos=c(20,0.85), log="")

plot(myconc, coef(test.m)[grep("d",names(coef(test.m)))], ylab="Max dead fraction", main="ixazomib")

This looks like it is working appropriately and the resultant dose–response curve could be fit by a LL4 model (lower bound should not be fixed to 0). However, the model fit values currently extend beyond the measured time range (e.g., Einf) and may not be accurate. A safer, albeit less elegant, way would be to simply capture the largest death fraction across the entire time course for each unique condition and plot those as the effect metric.

death_frac_max <- sapply(unique(d$uid), function(id) 
{
    z <- d[d$uid==id,]
    z <- z[z$orig.cell.count !=0,]
    out <- max(z$ch2.pos/z$orig.cell.count)
    out[is.infinite(out)] <- NA
    return(out)
})
Warning in max(z$ch2.pos/z$orig.cell.count) :
  no non-missing arguments to max; returning -Inf
Warning in max(z$ch2.pos/z$orig.cell.count) :
  no non-missing arguments to max; returning -Inf
Warning in max(z$ch2.pos/z$orig.cell.count) :
  no non-missing arguments to max; returning -Inf
Warning in max(z$ch2.pos/z$orig.cell.count) :
  no non-missing arguments to max; returning -Inf
Warning in max(z$ch2.pos/z$orig.cell.count) :
  no non-missing arguments to max; returning -Inf
Warning in max(z$ch2.pos/z$orig.cell.count) :
  no non-missing arguments to max; returning -Inf
Warning in max(z$ch2.pos/z$orig.cell.count) :
  no non-missing arguments to max; returning -Inf
Warning in max(z$ch2.pos/z$orig.cell.count) :
  no non-missing arguments to max; returning -Inf
Warning in max(z$ch2.pos/z$orig.cell.count) :
  no non-missing arguments to max; returning -Inf
Warning in max(z$ch2.pos/z$orig.cell.count) :
  no non-missing arguments to max; returning -Inf
Warning in max(z$ch2.pos/z$orig.cell.count) :
  no non-missing arguments to max; returning -Inf
Warning in max(z$ch2.pos/z$orig.cell.count) :
  no non-missing arguments to max; returning -Inf
Warning in max(z$ch2.pos/z$orig.cell.count) :
  no non-missing arguments to max; returning -Inf
Warning in max(z$ch2.pos/z$orig.cell.count) :
  no non-missing arguments to max; returning -Inf
Warning in max(z$ch2.pos/z$orig.cell.count) :
  no non-missing arguments to max; returning -Inf
Warning in max(z$ch2.pos/z$orig.cell.count) :
  no non-missing arguments to max; returning -Inf
Warning in max(z$ch2.pos/z$orig.cell.count) :
  no non-missing arguments to max; returning -Inf
Warning in max(z$ch2.pos/z$orig.cell.count) :
  no non-missing arguments to max; returning -Inf
Warning in max(z$ch2.pos/z$orig.cell.count) :
  no non-missing arguments to max; returning -Inf
Warning in max(z$ch2.pos/z$orig.cell.count) :
  no non-missing arguments to max; returning -Inf
Warning in max(z$ch2.pos/z$orig.cell.count) :
  no non-missing arguments to max; returning -Inf
Warning in max(z$ch2.pos/z$orig.cell.count) :
  no non-missing arguments to max; returning -Inf
Warning in max(z$ch2.pos/z$orig.cell.count) :
  no non-missing arguments to max; returning -Inf
Warning in max(z$ch2.pos/z$orig.cell.count) :
  no non-missing arguments to max; returning -Inf
Warning in max(z$ch2.pos/z$orig.cell.count) :
  no non-missing arguments to max; returning -Inf
Warning in max(z$ch2.pos/z$orig.cell.count) :
  no non-missing arguments to max; returning -Inf
Warning in max(z$ch2.pos/z$orig.cell.count) :
  no non-missing arguments to max; returning -Inf
Warning in max(z$ch2.pos/z$orig.cell.count) :
  no non-missing arguments to max; returning -Inf
Warning in max(z$ch2.pos/z$orig.cell.count) :
  no non-missing arguments to max; returning -Inf
Warning in max(z$ch2.pos/z$orig.cell.count) :
  no non-missing arguments to max; returning -Inf
Warning in max(z$ch2.pos/z$orig.cell.count) :
  no non-missing arguments to max; returning -Inf
Warning in max(z$ch2.pos/z$orig.cell.count) :
  no non-missing arguments to max; returning -Inf
Warning in max(z$ch2.pos/z$orig.cell.count) :
  no non-missing arguments to max; returning -Inf
Warning in max(z$ch2.pos/z$orig.cell.count) :
  no non-missing arguments to max; returning -Inf
Warning in max(z$ch2.pos/z$orig.cell.count) :
  no non-missing arguments to max; returning -Inf
Warning in max(z$ch2.pos/z$orig.cell.count) :
  no non-missing arguments to max; returning -Inf
Warning in max(z$ch2.pos/z$orig.cell.count) :
  no non-missing arguments to max; returning -Inf
Warning in max(z$ch2.pos/z$orig.cell.count) :
  no non-missing arguments to max; returning -Inf
Warning in max(z$ch2.pos/z$orig.cell.count) :
  no non-missing arguments to max; returning -Inf
Warning in max(z$ch2.pos/z$orig.cell.count) :
  no non-missing arguments to max; returning -Inf
Warning in max(z$ch2.pos/z$orig.cell.count) :
  no non-missing arguments to max; returning -Inf
Warning in max(z$ch2.pos/z$orig.cell.count) :
  no non-missing arguments to max; returning -Inf
Warning in max(z$ch2.pos/z$orig.cell.count) :
  no non-missing arguments to max; returning -Inf
Warning in max(z$ch2.pos/z$orig.cell.count) :
  no non-missing arguments to max; returning -Inf
Warning in max(z$ch2.pos/z$orig.cell.count) :
  no non-missing arguments to max; returning -Inf
names(death_frac_max) <- unique(d$uid)

s <- d[!duplicated(d$uid),]

# all(s$uid == names(death_frac_max))

s$death.frac.max <- death_frac_max

# Add 2D or 3D to cell line name to more easily distinguish
s$cell.line <- paste0(s$cell.line,"_",substr(s$plate.name,2,2),"D")

# Make cell.count into surviving percentage
s$cell.count <- signif((1 - s$death.frac.max) * 100,3)

# make time all 72h (not correct or relevant!)
s$time <- 72

Percent surviving dose–response curves

Using 1 - max death fraction as the effect metric, fit a 4-parameter log-logistic model (LL.4).

k <- s[s$plate.name=="V2-11T-D3" & s$drug1=="ixazomib",]
m <- drc::drm(cell.count ~ drug1.conc, data = k, fct = drc::LL.4())
plot(m, type="all", ylim=c(0,100), ylab="Percent surviving")

Iterate over unique drugs and plates (cell line and condition)

alldrugs <- unique(d$drug1)[-1] # remove "control"
all.m <- lapply(alldrugs, function(drug)
{
    z <- s[s$drug1==drug,]
    ucl <- unique(z$cell.line)
    cld <- lapply(ucl, function(cl) 
    {
        dtf <- z[z$cell.line==cl,]
        out <- tryCatch({drc::drm(cell.count ~ drug1.conc, data = dtf, fct = drc::LL.4())},
                 error=function(cond) {NA})
        return(out) 
    })
    names(cld) <- ucl
    return(cld)
})
Error in optim(startVec, opfct, hessian = TRUE, method = optMethod, control = list(maxit = maxIt,  : 
  non-finite finite-difference value [4]
Error in optim(startVec, opfct, hessian = TRUE, method = optMethod, control = list(maxit = maxIt,  : 
  non-finite finite-difference value [4]
Error in optim(startVec, opfct, hessian = TRUE, method = optMethod, control = list(maxit = maxIt,  : 
  non-finite finite-difference value [4]
Error in optim(startVec, opfct, hessian = TRUE, method = optMethod, control = list(maxit = maxIt,  : 
  non-finite finite-difference value [1]
Error in optim(startVec, opfct, hessian = TRUE, method = optMethod, control = list(maxit = maxIt,  : 
  non-finite finite-difference value [1]
Error in optim(startVec, opfct, hessian = TRUE, method = optMethod, control = list(maxit = maxIt,  : 
  non-finite finite-difference value [1]
Error in optim(startVec, opfct, hessian = TRUE, method = optMethod, control = list(maxit = maxIt,  : 
  non-finite value supplied by optim
Error in optim(startVec, opfct, hessian = TRUE, method = optMethod, control = list(maxit = maxIt,  : 
  non-finite finite-difference value [4]
Error in optim(startVec, opfct, hessian = TRUE, method = optMethod, control = list(maxit = maxIt,  : 
  non-finite finite-difference value [4]
Error in optim(startVec, opfct, hessian = TRUE, method = optMethod, control = list(maxit = maxIt,  : 
  non-finite finite-difference value [4]
Error in optim(startVec, opfct, hessian = TRUE, method = optMethod, control = list(maxit = maxIt,  : 
  non-finite finite-difference value [1]
Error in optim(startVec, opfct, hessian = TRUE, method = optMethod, control = list(maxit = maxIt,  : 
  non-finite finite-difference value [4]
Error in optim(startVec, opfct, hessian = TRUE, method = optMethod, control = list(maxit = maxIt,  : 
  non-finite finite-difference value [4]
Error in optim(startVec, opfct, hessian = TRUE, method = optMethod, control = list(maxit = maxIt,  : 
  non-finite finite-difference value [4]
Error in optim(startVec, opfct, hessian = TRUE, method = optMethod, control = list(maxit = maxIt,  : 
  non-finite finite-difference value [4]
Error in optim(startVec, opfct, hessian = TRUE, method = optMethod, control = list(maxit = maxIt,  : 
  non-finite finite-difference value [4]
Error in optim(startVec, opfct, hessian = TRUE, method = optMethod, control = list(maxit = maxIt,  : 
  non-finite finite-difference value [4]
Error in optim(startVec, opfct, hessian = TRUE, method = optMethod, control = list(maxit = maxIt,  : 
  non-finite finite-difference value [4]
Error in optim(startVec, opfct, hessian = TRUE, method = optMethod, control = list(maxit = maxIt,  : 
  non-finite finite-difference value [4]
Error in optim(startVec, opfct, hessian = TRUE, method = optMethod, control = list(maxit = maxIt,  : 
  non-finite finite-difference value [4]
Error in optim(startVec, opfct, hessian = TRUE, method = optMethod, control = list(maxit = maxIt,  : 
  non-finite finite-difference value [4]
Error in optim(startVec, opfct, hessian = TRUE, method = optMethod, control = list(maxit = maxIt,  : 
  non-finite finite-difference value [4]
Error in optim(startVec, opfct, hessian = TRUE, method = optMethod, control = list(maxit = maxIt,  : 
  non-finite finite-difference value [4]
Error in optim(startVec, opfct, hessian = TRUE, method = optMethod, control = list(maxit = maxIt,  : 
  non-finite finite-difference value [4]
Error in optim(startVec, opfct, hessian = TRUE, method = optMethod, control = list(maxit = maxIt,  : 
  non-finite finite-difference value [1]
Error in optim(startVec, opfct, hessian = TRUE, method = optMethod, control = list(maxit = maxIt,  : 
  non-finite finite-difference value [4]
Error in optim(startVec, opfct, hessian = TRUE, method = optMethod, control = list(maxit = maxIt,  : 
  non-finite finite-difference value [1]
Error in optim(startVec, opfct, hessian = TRUE, method = optMethod, control = list(maxit = maxIt,  : 
  non-finite finite-difference value [1]
Error in optim(startVec, opfct, hessian = TRUE, method = optMethod, control = list(maxit = maxIt,  : 
  non-finite finite-difference value [4]
Error in optim(startVec, opfct, hessian = TRUE, method = optMethod, control = list(maxit = maxIt,  : 
  non-finite finite-difference value [1]
Error in optim(startVec, opfct, hessian = TRUE, method = optMethod, control = list(maxit = maxIt,  : 
  non-finite finite-difference value [4]
Error in optim(startVec, opfct, hessian = TRUE, method = optMethod, control = list(maxit = maxIt,  : 
  non-finite finite-difference value [4]
Error in optim(startVec, opfct, hessian = TRUE, method = optMethod, control = list(maxit = maxIt,  : 
  non-finite finite-difference value [4]
Error in optim(startVec, opfct, hessian = TRUE, method = optMethod, control = list(maxit = maxIt,  : 
  non-finite finite-difference value [4]
Error in optim(startVec, opfct, hessian = TRUE, method = optMethod, control = list(maxit = maxIt,  : 
  non-finite finite-difference value [4]
names(all.m) <- alldrugs
plotMultiCurve(unlist(all.m, recursive=FALSE)[9:16], ylim=c(0,100))

Save surviving percentage data for Thunor

Still need unique plate id (upid)

s$upid <- paste(s$expt.id,s$plate.name,sep="_")

# remove NAs
s <- s[!is.na(s$cell.count),]

fn <- "../data/VLW_001+V23b_SurvPct_Thunor_dataset.csv"

if(!file.exists(fn) | OVERWRITE) write.csv(s, file=fn, row.names=FALSE)

Import Thunor curve-fit data

Data uploaded to Thunor (VLW_SurvPercent) were processed and the Viability Parameters were downloaded as .

v <- read.csv('../data/VLW_SurvPercent_viability_params.tsv', sep="\t")

Order the drugs by aa_obs (highest to lowest) and save subset with aa_obs < 0.5 to object b. Count how many of each cell line are found in this subset (looking for bias in cell lines or 2D/3D condition)

v <- v[order(v$aa_obs, decreasing=TRUE),]
b <- v[v$aa_obs > 0.5,]
diprate::nEach(b$cell_line)
16T_2D 11T_2D 11T_3D 29T_2D 16T_3D 21T_2D 29T_3D 21T_3D 
    18     15     22     12     26      6     13     17 

16T_3D looks overrepresented and 29T_3D looks underrepresented.

b[b$cell_line=="29T_3D",]

Sort values by drugs

Looks at sum of aa_obs as metric of overall effect. Also get sd to assess similarity across cell lines and 2D/3D.

k <- lapply(unique(v$drug), function(x) 
              v[v$drug==x,c("cell_line","aa_obs")])
names(k) <- as.character(unique(v$drug))

a <- sapply(names(k), function(x) sum(k[[x]]$aa_obs))
a <- sort(a, decreasing=TRUE)

# reorder k to match a
k <- k[names(a)]

std.dev <- sapply(names(k), function(x) sd(k[[x]]$aa_obs))
cv <- sapply(names(k), function(x) sd(k[[x]]$aa_obs)/mean(k[[x]]$aa_obs))

av <- list(orig.data=k, sum.aa_obs=a, std.dev=std.dev, cv=cv)

Include DIP rate-based (observed) Emax values from 2D condition for each cell line

First, load all DIP rate-based parameters (DIP parameters downloaded from DipDB). Extract the emax_obs values from each cell line for each drug as a new data.frame (saved in emo for emax_obs).

d <- read.csv("../data/VLW_23ab_2D_only_dip_params.tsv", sep="\t", as.is=TRUE)

# emo = emax_obs
emo <- data.frame(do.call(rbind,lapply(unique(d$drug), function(x) d[d$drug==x,"emax_obs"])))
colnames(emo) <- paste0("emax_obs_",unique(d$cell_line))
rownames(emo) <- unique(d$drug)

Organize a data.frame to save for Kensey and Vivian.

LS0tCnRpdGxlOiAiQW5hbHlzaXMgb2YgVkxXIDJEdnMzRCBjb21iaW5lZCBkYXRhLCBwYXJ0IDIiCm91dHB1dDogCiAgICBodG1sX25vdGVib29rOgogICAgICAgIHNlbGZfY29udGFpbmVkOiB5ZXMKYXV0aG9yOiAiRGFycmVuIFIgVHlzb24iCmRhdGU6ICIyMDIxLTAyLTE1IgotLS0KCiMjIyBBbmFseXNpcyBvZiBkZWFkIGNlbGwgZnJhY3Rpb25zCkluIHBhcnQgMSAoYFZMV19WMjNfY29tYmluZWRfYW5hbHlzaXMuUm1kYCksIGFsbCBkYXRhIHdlcmUgb3JnYW5pemVkIHRvIGluY2x1ZGUgZHJ1ZyBhbm5vdGF0aW9uIGFuZCBpbWFnZSBmaWxlIHBhdGhzLCBpbiBhZGRpdGlvbiB0byBjZWxsIGNvdW50IGFuZCBjaDItcG9zIGNvdW50cy4gVXNlIHRoZXNlIGRhdGEgdG8gZXhhbWluZSB0aW1lIGFuZCBkcnVnIGNvbmNlbnRyYXRpb24gZWZmZWN0cyBvbiBjZWxsIGRlYXRoIGtpbmV0aWNzLgoKYGBge3IgU2V0dXB9CmxpYnJhcnkoZGlwcmF0ZSkKCk9WRVJXUklURSA8LSBGQUxTRQpgYGAKCkZvciB0aGVzZSBhbmFseXNlcyB3ZSBkbyBub3QgbmVlZCBgZmlsZV9uYW1lYApgYGB7ciBMb2FkIGRhdGF9CmQgPC0gcmVhZC5jc3YoIi4uL2RhdGEvVkxXX1YyM2FiX2RhdGFzZXQuY3N2IiwgYXMuaXM9VFJVRSkKZCA8LSBkW29yZGVyKGQkdWlkLGQkdGltZSksXQpkJGZpbGVfbmFtZSA8LSBOVUxMCmBgYAoKYGBge3IgUHJlcCBjb3VudHMgYnkgY2VsbCBsaW5lLCBpbmNsdWRlPUZBTFNFfQpwZCA8LSBwcmVwQ291bnRCeUNMKGRbZ3JlcGwoIlYyIixkJHBsYXRlLm5hbWUpLF0pCmBgYAoKRXhhbWluZSB0aGUgZGF0YSBmcm9tIHRoZSBwcmV2aW91c2x5IGlkZW50aWZpZWQgaGl0cy4gRm9jdXMgb24gdHdvIGNlbGwgbGluZXMuIDE2VCBiZXN0LWxvb2tpbmcgM0Qgc3RydWN0dXJlcywgMjFUIG1vc3QgY2xpbmljYWxseSByZWxldmFudCwgYnV0IHdvcnN0IGxvb2tpbmcgYW5kIHNsb3dlc3QgZ3Jvd2luZy4KCiogcHJhbGF0cmV4YXRlIChsb3cgcG90ZW5jeSkgYW5kIGl4YXpvbWliIChoaWdoIHBvdGVuY3kgYW5kIGVmZmljYWN5KSBvbiAxNlQgYW5kIDIxVC4KCmBgYHtyIDJEIGRhdGEgc3Vic2V0LCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpteWRydWdzIDwtIGMoImJvcnRlem9taWIiLCAiaXhhem9taWIiLCAiWU0xNTUiLCAiWmlib3RlbnRhbiIsICJSb21pZGVwc2luIiwgImNhYmF6aXRheGVsIiwgImRlbGFuem9taWIiLAogICAgICAgICAgICAgImlzcGluZXNpYiIsICJLWDIzOTEiLCAidHJpcHRvbGlkZSIsICJjYXJmaWx6b21pYiIsICJyaWdvc2VydGliIiwgInByYWxhdHJleGF0ZSIpCm15Y2wgPC0gYygiMTZUIiwiMjFUIikKczJkIDwtIGxhcHBseShteWNsLCBmdW5jdGlvbihjbCkgZFtkJGRydWcxICVpbiUgbXlkcnVncyAmIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGQkY2VsbC5saW5lPT1jbCAmIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGdyZXBsKCJWMiIsZCR1aWQpLF0pCmBgYAoKU2F2ZSBkcnVnIHN1YnNldHMgaW50byBvYmplY3RzLgoKYGBge3IgMkQgZGF0YSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KYSA8LSBzdWJzZXQoczJkW1sxXV0sIGRydWcxICVpbiUgbXlkcnVncykKYiA8LSBzdWJzZXQoczJkW1syXV0sIGRydWcxICVpbiUgbXlkcnVncykKYGBgCgoKYGBge3IgM0QgZGF0YSBzdWJzZXQsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CnMzZCA8LSBsYXBwbHkobXljbCwgZnVuY3Rpb24obikgZFtkJGRydWcxICVpbiUgbXlkcnVncyAmIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGQkY2VsbC5saW5lPT1uICYgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZ3JlcGwoIlYzIixkJHVpZCksXSkKYGBgCgojIyMjIFNjcmVlbiAzRCBkYXRhIGJ5IGNlbGwgY291bnQgPiA0MApNYW55IDNEIGN1bHR1cmVzIGhhZCBmZXcsIGlmIGFueSwgY2VsbHMgaW4gdGhlIGZpZWxkIG9mIHZpZXcuIE11c3QgZXhjbHVkZS4KYGBge3IgM0QgZGF0YX0KYTMgPC0gc3Vic2V0KHMzZFtbMV1dLCBkcnVnMSAlaW4lIG15ZHJ1Z3MgJiBvcmlnLmNlbGwuY291bnQgPj0gNDApCmIzIDwtIHN1YnNldChzM2RbWzJdXSwgZHJ1ZzEgJWluJSBteWRydWdzICYgb3JpZy5jZWxsLmNvdW50ID49IDQwKQpgYGAKCiMjIyAxNlQgMkQKRXhhbWluZSB0aGUgZGVhdGgga2luZXRpY3MgKGZyYWN0aW9uIG9mIGNoMiBwb3NpdGl2ZSBjZWxscyBvdmVyIHRpbWUpLgpgYGB7ciAxNlQgMkQsIGZpZy5oZWlnaHQ9NSwgZmlnLndpZHRoPTh9CnBsb3RBbGxDaDIobGlzdCgiMTZUIj1hKSwgY291bnRfY249Im9yaWcuY2VsbC5jb3VudCIpCmBgYAoKIyMjIDE2VCAzRApgYGB7ciAxNlQgM0QsIGZpZy5oZWlnaHQ9NSwgZmlnLndpZHRoPTh9CnBsb3RBbGxDaDIobGlzdCgiMTZUIj1hMyksIGNvdW50X2NuPSJvcmlnLmNlbGwuY291bnQiKQpgYGAKIyMjIDIxVCAyRApgYGB7ciAyMVQgMkQsIGZpZy5oZWlnaHQ9NSwgZmlnLndpZHRoPTh9CnBsb3RBbGxDaDIobGlzdCgiMjFUIj1iKSwgY291bnRfY249Im9yaWcuY2VsbC5jb3VudCIpCmBgYAoKIyMjIDIxVCAzRApgYGB7ciAyMVQgM0QsIGZpZy5oZWlnaHQ9NSwgZmlnLndpZHRoPTh9CnBsb3RBbGxDaDIobGlzdCgiMjFUIj1iMyksIGNvdW50X2NuPSJvcmlnLmNlbGwuY291bnQiKQpgYGAKCgojIyMgQXBwbHkgc2ltcGxlIGxvZ2lzdGljIG1vZGVsIHRvIGRlYXRoIGtpbmV0aWNzIGRhdGEKCmBgYHtyIGxvZ2lzdGljIGZ1bmN0aW9ufQpteWwzIDwtIGZ1bmN0aW9uKHgsIG1pbj0wLG1heD0wLjUsbWlkX3g9NDgsIGRyPTAuMDUpIG1heC8oMSArIGV4cCgtZHIqKHgtbWlkX3gpKSkgCmBgYAoKCmBgYHtyIHRlc3QgcGxvdCBvZiBsb2dpc3RpYyBmdW5jdGlvbn0KY3VydmUobXlsMywgZnJvbT0wLCB0bz0xMjApCmBgYAoKIyMjIFVzZSBEUkMgMy1wYXJhbSBsb2dpc3RpYyBmdW5jdGlvbiB0byBmaXQgZGF0YQpUaGUgYGRyY2AgbGlicmFyeSBoYXMgYSBudW1iZXIgb2YgZG9zZS0tcmVzcG9uc2UgbW9kZWwgZml0dGluZyBhbGdvcml0aG1zLiBUaGUgYEwuM2AgbW9kZWwgaXMgYSAzLXBhcmFtZXRlciBsb2dpc3RpYyAobm90IGxvZy1sb2dpc3RpYykgbW9kZWwgdGhhdCBjYW4gYmUgYXBwbGllZCB0byB0aGUgdGltZSBjb3Vyc2UgZGF0YS4gIAoKVGVzdCB0aGlzIHdpdGggYSBzdWJzZXQgb2YgdGhlIGRhdGEgKDIxVCArIGRlbGFuem9taWIpLgoKVGhlIG1vZGVsIGlzIHRyeWluZyB0byBleHBsYWluIHRoZSBjaGFuZ2UgaW4gZGVhZCBjZWxsIGZyYWN0aW9uIG92ZXIgdGltZS4KCmBgYHtyIGRlbGFuem9taWJ9Cm15ZGF0IDwtIGJbYiRkcnVnMT09ImRlbGFuem9taWIiLF0KdGVzdC5tIDwtIGRyYzo6ZHJtKGNoMi5wb3Mvb3JpZy5jZWxsLmNvdW50IH4gdGltZSwgZmFjdG9yKHNpZ25pZihsb2cxMChkcnVnMS5jb25jKSwzKSksIAogICAgICAgICAgICAgICAgICAgZGF0YSA9IG15ZGF0LCBmY3QgPSBkcmM6OkwuMygpKQoKcGxvdCh0ZXN0Lm0sIGxlZ2VuZFBvcz1jKDIwLDAuODUpLCBsb2c9IiIpCmBgYAojIyMgRXh0cmFjdCBtYXhpbXVtIGRlYWQgY2VsbCBmcmFjdGlvbiBmcm9tIGN1cnZlcwpQdWxsIGZyb20gZml0IGNvZWZmaWNpZW50cyAodXBwZXIgYm91bmQ7IGNvZWZmaWNpZW50ICJkIikuCmBgYHtyIE1heCBkZWFkIGNlbGwgZnJhY3Rpb259Cm15Y29uYyA8LSBuYW1lcyhjb2VmKHRlc3QubSkpW2dyZXAoImQiLG5hbWVzKGNvZWYodGVzdC5tKSkpXQpteWNvbmMgPC0gZ3N1YigiZDoiLCIiLG15Y29uYykKbXljb25jIDwtIGFzLm51bWVyaWMobXljb25jKQoKcGxvdChteWNvbmMsIGNvZWYodGVzdC5tKVtncmVwKCJkIixuYW1lcyhjb2VmKHRlc3QubSkpKV0sIHlsYWI9Ik1heCBkZWFkIGZyYWN0aW9uIiwgbWFpbj0iZGVsYW56b21pYiIpCmBgYAojIyMgRXhhbWluZSBhbiBkaWZmZXJlbnQgZHJ1ZwpEZXRlcm1pbmUgd2hldGhlciBwcmVkaWN0ZWQgdGltZSBjb3Vyc2UgdHJhamVjdG9yaWVzIG1hdGNoIHRob3NlIG9mIHRoZSBpeGF6b21pYi10cmVhdGVkIGNlbGxzLgpgYGB7ciBpeGF6b21pYn0KbXlkYXQgPC0gYltiJGRydWcxPT0iaXhhem9taWIiLF0KdGVzdC5tIDwtIGRyYzo6ZHJtKGNoMi5wb3Mvb3JpZy5jZWxsLmNvdW50IH4gdGltZSwgZmFjdG9yKHNpZ25pZihsb2cxMChkcnVnMS5jb25jKSwzKSksIAogICAgICAgICAgICAgICAgICAgZGF0YSA9IG15ZGF0LCBmY3QgPSBkcmM6OkwuMygpKQoKcGxvdCh0ZXN0Lm0sIGxlZ2VuZFBvcz1jKDIwLDAuODUpLCBsb2c9IiIpCmBgYAoKYGBge3IgUGxvdCBpeGF6b21pYiBEUkN9CnBsb3QobXljb25jLCBjb2VmKHRlc3QubSlbZ3JlcCgiZCIsbmFtZXMoY29lZih0ZXN0Lm0pKSldLCB5bGFiPSJNYXggZGVhZCBmcmFjdGlvbiIsIG1haW49Iml4YXpvbWliIikKCmBgYAoKVGhpcyBsb29rcyBsaWtlIGl0IGlzIHdvcmtpbmcgYXBwcm9wcmlhdGVseSBhbmQgdGhlIHJlc3VsdGFudCBkb3NlLS1yZXNwb25zZSBjdXJ2ZSBjb3VsZCBiZSBmaXQgYnkgYSBMTDQgbW9kZWwgKGxvd2VyIGJvdW5kIHNob3VsZCBub3QgYmUgZml4ZWQgdG8gMCkuIEhvd2V2ZXIsIHRoZSBtb2RlbCBmaXQgdmFsdWVzIGN1cnJlbnRseSBleHRlbmQgYmV5b25kIHRoZSBtZWFzdXJlZCB0aW1lIHJhbmdlIChlLmcuLCBFaW5mKSBhbmQgbWF5IG5vdCBiZSBhY2N1cmF0ZS4gQSBzYWZlciwgYWxiZWl0IGxlc3MgZWxlZ2FudCwgd2F5IHdvdWxkIGJlIHRvIHNpbXBseSBjYXB0dXJlIHRoZSBsYXJnZXN0IGRlYXRoIGZyYWN0aW9uIGFjcm9zcyB0aGUgZW50aXJlIHRpbWUgY291cnNlIGZvciBlYWNoIHVuaXF1ZSBjb25kaXRpb24gYW5kIHBsb3QgdGhvc2UgYXMgdGhlIGVmZmVjdCBtZXRyaWMuCgpgYGB7ciBtYXggZGVhdGggZnJhY3Rpb259CmRlYXRoX2ZyYWNfbWF4IDwtIHNhcHBseSh1bmlxdWUoZCR1aWQpLCBmdW5jdGlvbihpZCkgCnsKICAgIHogPC0gZFtkJHVpZD09aWQsXQogICAgeiA8LSB6W3okb3JpZy5jZWxsLmNvdW50ICE9MCxdCiAgICBvdXQgPC0gbWF4KHokY2gyLnBvcy96JG9yaWcuY2VsbC5jb3VudCkKICAgIG91dFtpcy5pbmZpbml0ZShvdXQpXSA8LSBOQQogICAgcmV0dXJuKG91dCkKfSkKbmFtZXMoZGVhdGhfZnJhY19tYXgpIDwtIHVuaXF1ZShkJHVpZCkKCnMgPC0gZFshZHVwbGljYXRlZChkJHVpZCksXQoKIyBhbGwocyR1aWQgPT0gbmFtZXMoZGVhdGhfZnJhY19tYXgpKQoKcyRkZWF0aC5mcmFjLm1heCA8LSBkZWF0aF9mcmFjX21heAoKIyBBZGQgMkQgb3IgM0QgdG8gY2VsbCBsaW5lIG5hbWUgdG8gbW9yZSBlYXNpbHkgZGlzdGluZ3Vpc2gKcyRjZWxsLmxpbmUgPC0gcGFzdGUwKHMkY2VsbC5saW5lLCJfIixzdWJzdHIocyRwbGF0ZS5uYW1lLDIsMiksIkQiKQoKIyBNYWtlIGNlbGwuY291bnQgaW50byBzdXJ2aXZpbmcgcGVyY2VudGFnZQpzJGNlbGwuY291bnQgPC0gc2lnbmlmKCgxIC0gcyRkZWF0aC5mcmFjLm1heCkgKiAxMDAsMykKCiMgbWFrZSB0aW1lIGFsbCA3MmggKG5vdCBjb3JyZWN0IG9yIHJlbGV2YW50ISkKcyR0aW1lIDwtIDcyCmBgYAoKIyMjIFBlcmNlbnQgc3Vydml2aW5nIGRvc2UtLXJlc3BvbnNlIGN1cnZlcwpVc2luZyAxIC0gbWF4IGRlYXRoIGZyYWN0aW9uIGFzIHRoZSBlZmZlY3QgbWV0cmljLCBmaXQgYSA0LXBhcmFtZXRlciBsb2ctbG9naXN0aWMgbW9kZWwgKExMLjQpLiAKYGBge3IgREZNIERSQywgd2FybmluZz1GQUxTRX0KayA8LSBzW3MkcGxhdGUubmFtZT09IlYyLTExVC1EMyIgJiBzJGRydWcxPT0iaXhhem9taWIiLF0KbSA8LSBkcmM6OmRybShjZWxsLmNvdW50IH4gZHJ1ZzEuY29uYywgZGF0YSA9IGssIGZjdCA9IGRyYzo6TEwuNCgpKQpwbG90KG0sIHR5cGU9ImFsbCIsIHlsaW09YygwLDEwMCksIHlsYWI9IlBlcmNlbnQgc3Vydml2aW5nIikKYGBgCgojIyMgSXRlcmF0ZSBvdmVyIHVuaXF1ZSBkcnVncyBhbmQgcGxhdGVzIChjZWxsIGxpbmUgYW5kIGNvbmRpdGlvbikKYGBge3IgQWxsIGRydWdzIHN1cnZpdmluZyBmcmFjdGlvbiwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KYWxsZHJ1Z3MgPC0gdW5pcXVlKGQkZHJ1ZzEpWy0xXSAjIHJlbW92ZSAiY29udHJvbCIKYWxsLm0gPC0gbGFwcGx5KGFsbGRydWdzLCBmdW5jdGlvbihkcnVnKQp7CiAgICB6IDwtIHNbcyRkcnVnMT09ZHJ1ZyxdCiAgICB1Y2wgPC0gdW5pcXVlKHokY2VsbC5saW5lKQogICAgY2xkIDwtIGxhcHBseSh1Y2wsIGZ1bmN0aW9uKGNsKSAKICAgIHsKICAgICAgICBkdGYgPC0gelt6JGNlbGwubGluZT09Y2wsXQogICAgICAgIG91dCA8LSB0cnlDYXRjaCh7ZHJjOjpkcm0oY2VsbC5jb3VudCB+IGRydWcxLmNvbmMsIGRhdGEgPSBkdGYsIGZjdCA9IGRyYzo6TEwuNCgpKX0sCiAgICAgICAgICAgICAgICAgZXJyb3I9ZnVuY3Rpb24oY29uZCkge05BfSkKICAgICAgICByZXR1cm4ob3V0KSAKICAgIH0pCiAgICBuYW1lcyhjbGQpIDwtIHVjbAogICAgcmV0dXJuKGNsZCkKfSkKCm5hbWVzKGFsbC5tKSA8LSBhbGxkcnVncwpgYGAKCmBgYHtyIHBsb3QgRFJDfQpwbG90TXVsdGlDdXJ2ZSh1bmxpc3QoYWxsLm0sIHJlY3Vyc2l2ZT1GQUxTRSlbOToxNl0sIHlsaW09YygwLDEwMCkpCmBgYAoKIyMjIFNhdmUgc3Vydml2aW5nIHBlcmNlbnRhZ2UgZGF0YSBmb3IgVGh1bm9yClN0aWxsIG5lZWQgdW5pcXVlIHBsYXRlIGlkIChgdXBpZGApIApgYGB7ciBTYXZlIHN1cnYgZm9yIFRodW5vcn0KcyR1cGlkIDwtIHBhc3RlKHMkZXhwdC5pZCxzJHBsYXRlLm5hbWUsc2VwPSJfIikKCiMgcmVtb3ZlIE5BcwpzIDwtIHNbIWlzLm5hKHMkY2VsbC5jb3VudCksXQoKZm4gPC0gIi4uL2RhdGEvVkxXXzAwMStWMjNiX1N1cnZQY3RfVGh1bm9yX2RhdGFzZXQuY3N2IgoKaWYoIWZpbGUuZXhpc3RzKGZuKSB8IE9WRVJXUklURSkgd3JpdGUuY3N2KHMsIGZpbGU9Zm4sIHJvdy5uYW1lcz1GQUxTRSkKYGBgCgojIyMgSW1wb3J0IFRodW5vciBjdXJ2ZS1maXQgZGF0YQpEYXRhIHVwbG9hZGVkIHRvIFRodW5vciAoYFZMV19TdXJ2UGVyY2VudGApIHdlcmUgcHJvY2Vzc2VkIGFuZCB0aGUgYFZpYWJpbGl0eSBQYXJhbWV0ZXJzYCB3ZXJlIGRvd25sb2FkZWQgYXMgLgoKYGBge3J9CnYgPC0gcmVhZC5jc3YoJy4uL2RhdGEvVkxXX1N1cnZQZXJjZW50X3ZpYWJpbGl0eV9wYXJhbXMudHN2Jywgc2VwPSJcdCIpCmBgYAoKT3JkZXIgdGhlIGRydWdzIGJ5IGBhYV9vYnNgIChoaWdoZXN0IHRvIGxvd2VzdCkgYW5kIHNhdmUgc3Vic2V0IHdpdGggYGFhX29icyA8IDAuNWAgdG8gb2JqZWN0IGBiYC4gQ291bnQgaG93IG1hbnkgb2YgZWFjaCBjZWxsIGxpbmUgYXJlIGZvdW5kIGluIHRoaXMgc3Vic2V0IChsb29raW5nIGZvciBiaWFzIGluIGNlbGwgbGluZXMgb3IgMkQvM0QgY29uZGl0aW9uKQpgYGB7cn0KdiA8LSB2W29yZGVyKHYkYWFfb2JzLCBkZWNyZWFzaW5nPVRSVUUpLF0KYiA8LSB2W3YkYWFfb2JzID4gMC41LF0KZGlwcmF0ZTo6bkVhY2goYiRjZWxsX2xpbmUpCmBgYAoxNlRfM0QgbG9va3Mgb3ZlcnJlcHJlc2VudGVkIGFuZCAyOVRfM0QgbG9va3MgdW5kZXJyZXByZXNlbnRlZC4KCmBgYHtyfQpiW2IkY2VsbF9saW5lPT0iMjlUXzNEIixdCmBgYAoKIyMjIFNvcnQgdmFsdWVzIGJ5IGRydWdzCkxvb2tzIGF0IHN1bSBvZiBhYV9vYnMgYXMgbWV0cmljIG9mIG92ZXJhbGwgZWZmZWN0LgpBbHNvIGdldCBzZCB0byBhc3Nlc3Mgc2ltaWxhcml0eSBhY3Jvc3MgY2VsbCBsaW5lcyBhbmQgMkQvM0QuCmBgYHtyfQprIDwtIGxhcHBseSh1bmlxdWUodiRkcnVnKSwgZnVuY3Rpb24oeCkgCiAgICAgICAgICAgICAgdlt2JGRydWc9PXgsYygiY2VsbF9saW5lIiwiYWFfb2JzIildKQpuYW1lcyhrKSA8LSBhcy5jaGFyYWN0ZXIodW5pcXVlKHYkZHJ1ZykpCgphIDwtIHNhcHBseShuYW1lcyhrKSwgZnVuY3Rpb24oeCkgc3VtKGtbW3hdXSRhYV9vYnMpKQphIDwtIHNvcnQoYSwgZGVjcmVhc2luZz1UUlVFKQoKIyByZW9yZGVyIGsgdG8gbWF0Y2ggYQprIDwtIGtbbmFtZXMoYSldCgpzdGQuZGV2IDwtIHNhcHBseShuYW1lcyhrKSwgZnVuY3Rpb24oeCkgc2Qoa1tbeF1dJGFhX29icykpCmN2IDwtIHNhcHBseShuYW1lcyhrKSwgZnVuY3Rpb24oeCkgc2Qoa1tbeF1dJGFhX29icykvbWVhbihrW1t4XV0kYWFfb2JzKSkKCmF2IDwtIGxpc3Qob3JpZy5kYXRhPWssIHN1bS5hYV9vYnM9YSwgc3RkLmRldj1zdGQuZGV2LCBjdj1jdikKCmBgYAoKIyMjIEluY2x1ZGUgRElQIHJhdGUtYmFzZWQgKG9ic2VydmVkKSBFbWF4IHZhbHVlcyBmcm9tIDJEIGNvbmRpdGlvbiBmb3IgZWFjaCBjZWxsIGxpbmUKRmlyc3QsIGxvYWQgYWxsIERJUCByYXRlLWJhc2VkIHBhcmFtZXRlcnMgKERJUCBwYXJhbWV0ZXJzIGRvd25sb2FkZWQgZnJvbSBbRGlwREJdKGh0dHBzOi8vZGlwZGIubG9sYWIueHl6L2RhdGFzZXQvMTkzKSkuIEV4dHJhY3QgdGhlIGBlbWF4X29ic2AgdmFsdWVzIGZyb20gZWFjaCBjZWxsIGxpbmUgZm9yIGVhY2ggZHJ1ZyBhcyBhIG5ldyBkYXRhLmZyYW1lIChzYXZlZCBpbiBgZW1vYCBmb3IgZW1heF9vYnMpLgoKYGBge3J9CmQgPC0gcmVhZC5jc3YoIi4uL2RhdGEvVkxXXzIzYWJfMkRfb25seV9kaXBfcGFyYW1zLnRzdiIsIHNlcD0iXHQiLCBhcy5pcz1UUlVFKQoKIyBlbW8gPSBlbWF4X29icwplbW8gPC0gZGF0YS5mcmFtZShkby5jYWxsKHJiaW5kLGxhcHBseSh1bmlxdWUoZCRkcnVnKSwgZnVuY3Rpb24oeCkgZFtkJGRydWc9PXgsImVtYXhfb2JzIl0pKSkKY29sbmFtZXMoZW1vKSA8LSBwYXN0ZTAoImVtYXhfb2JzXyIsdW5pcXVlKGQkY2VsbF9saW5lKSkKcm93bmFtZXMoZW1vKSA8LSB1bmlxdWUoZCRkcnVnKQpgYGAKCk9yZ2FuaXplIGEgZGF0YS5mcmFtZSB0byBzYXZlIGZvciBLZW5zZXkgYW5kIFZpdmlhbi4KYGBge3J9Cm8gPC0gZGF0YS5mcmFtZSh2aWFiaWxpdHkuYWFfb2JzLnN1bT1hLCB2aWFiaWxpdHkuYWFfb2JzLnN0ZC5kZXY9c3RkLmRldiwgdmlhYmlsaXR5LmFhX29icy5jdj1jdikKaWYoIWZpbGUuZXhpc3RzKGZuKSB8IE9WRVJXUklURSkgd3JpdGUuY3N2KG8sIGZpbGU9Ii4uL2RhdGEvbWF4X2RlYXRoX0RSQ19wYXJhbXMuY3N2Iiwgcm93Lm5hbWVzPUZBTFNFKQpoZWFkKG8pCmBgYAoK